#ifndef DATATABLE_H_
#define DATATABLE_H_

#include "scHeader.h"
#include "DataObject.h"
#include "Request.h"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/ordered_index.hpp>

namespace sybilpm
{
struct CacheTag
{
	CacheTag(const Request * request, TimeType timestamp) : rq(*request), tst(timestamp), touched(false) {}
	bool isTouched() const {return touched;}
	void clearTouched() {touched=false;}
	void setTouched() {touched=true;}
	Request rq;
	TimeType tst;
private:
	CacheTag(); // intentionally no implementation
	bool touched;
};
typedef std::list<CacheTag> CacheTags;

struct DataTable_
{
	DataTable_(PkType tableId, const ColDescs * attributes = 0);
	bool modifyTable(const QXmlAttributes & attributes) {return false; Q_UNUSED(attributes)} // maybe unnecessary
	void modifyCache(const Request * request, TimeType timestamp);
	bool modifyData(const QXmlAttributes & attributes, DataUserRole userRole =DUR_CLI);
	/**
	 * Makes sure that all indices work on up-to-date values. Will always use
	 * the client DataObjects, so don't call it for server-side data - server-side
	 * objects are expected to be not modified but discarded and read again.
	 * The reference based method does nothing if the PK is not found. (You must
	 * not change the PK anyway.)
	 * @return true if something was changed
	 */
	bool makeConsistent(const DataObject & dobj);
	bool makeConsistent(DOsPkIdx::const_iterator doIt);
	DataObjects & getDos(DataUserRole userRole)
		{switch(userRole){case DUR_CLI: case DUR_UNDEF: return dos; case DUR_SRV: return sdos;
		case DUR_CLITMP: return tdos;}}
	const DataObjects & getDos(DataUserRole userRole) const
		{switch(userRole){case DUR_CLI: case DUR_UNDEF: return dos; case DUR_SRV: return sdos;
		case DUR_CLITMP: return tdos;}}
	void toXml(QXmlStreamWriter * writer, PkType pk, DataUserRole userRole) const;
	void clear() {cacheTags.clear(); dirty=false; dos.clear();}
	bool isDirty() const {return dirty;}
	void clearDirty() {dirty=false;}
	void setDirty() {dirty=true;}
	void clearTouchedAll();
	void setTouched(const Request & request);
	void cleanGarbage();
	PkType parentPk;
	CacheTags cacheTags;
	int niceNameCol;
	ColDescs attrs;
	DataObjects dos, tdos, sdos;
	bool dirty;
};

/**
 * Generic data structure like a database table. It can store 3 different
 * "flavors" of data:
 *   1) Client data (DUR_CLI): All caching- and garbage collection-mechanisms
 *      work against this data. It is the default data user role.
 *   2) Client temporary data (DUR_CLITMP): Used during data reconciliation.
 *   3) Server side data (DUR_SRV): Temporary data used during server requests
 *      which are served by the "local server".
 * DUR_CLITMP and DUR_SRV support basically insertion and access through
 * indices, so be careful if you expect any special behavior if you don't
 * work with DUR_CLI.
 * Table meta info is kept redundantly in DataObjetcs of m_entities and
 * in ColDescs of DataTable. You may query whichever you like more.
 */
class DataTable
{
public:
	struct PkTag{};
	struct TechNameTag{};

	DataTable(PkType tableId, const char * techName, QString name, const char * techRowName, QString rowName, DataDomain domain, const ColDescs * attributes = 0);
	virtual ~DataTable();
	PkType getPk() const {return pk;}
	const std::string & getTechName() const {return techName;}
	QString getTechNameQ() const {return techNameQ;}
	QString getNameQ() const {return name;}
	const std::string & getTechRowName() const {return techRowName;}
	QString getTechRowNameQ() const {return techRowNameQ;}
	DataDomain getDomain() const {return domain;}
	/**
	 * @return true if the request is already covered by cached data
	 */
	bool scopeCovered(const Request * scope) const;

	/**
	 * get a DataObject based on it's IK and a timeslice.
	 */
	const DataObject * getByIk(PkType ik, TimeType deadline) const;
	const DataObject * getByPk(PkType doPk, DataUserRole userRole =DUR_CLI) const;

	/**
	 * Get a volatile access to the payload. This is how we tell boost::multi_index
	 * that the indices will stay valid although data may be modified. So make
	 * sure, that indexed attributes/formulas NEVER depend on payload content (anything in
	 * DataTable_)!
	 */
	DataTable_ & getPayload() const {return const_cast<DataTable_ &>(pl);}

	/**
	 * These are helper methods for the lcal DB server.
	 */
	QString getAttrList() const;
	QString getAttrValueList(const PkType pk) const;


private:
	PkType pk;
	std::string techName, techRowName;
	QString techNameQ, techRowNameQ, name, rowName;
	DataDomain domain;
	DataTable_ pl;
};

typedef boost::multi_index_container<DataTable,
	boost::multi_index::indexed_by<
		boost::multi_index::ordered_unique<boost::multi_index::tag<DataTable::PkTag>,
			BOOST_MULTI_INDEX_CONST_MEM_FUN(DataTable,PkType,getPk)>,
		boost::multi_index::ordered_unique<boost::multi_index::tag<DataTable::TechNameTag>,
			BOOST_MULTI_INDEX_CONST_MEM_FUN(DataTable,const std::string&,getTechName)> >
> DataTables;

typedef       DataTables::index<DataTable::PkTag         >::type DTsPkIdx;
typedef const DataTables::index<DataTable::PkTag         >::type DTsCPkIdx;
typedef       DataTables::index<DataTable::TechNameTag   >::type DTsTechNameIdx;
typedef const DataTables::index<DataTable::TechNameTag   >::type DTsCTechNameIdx;

} // namespace sybilpm

#endif /* DATATABLE_H_ */
